// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modcmd

import (
	"context"
	"encoding/json"
	"os"
	"runtime"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/modload"

	"golang.org/x/mod/module"
)

var cmdDownload = &base.Command{
	UsageLine: "go mod download [-x] [-json] [modules]",
	Short:     "download modules to local cache",
	Long: `
Download downloads the named modules, which can be module patterns selecting
dependencies of the main module or module queries of the form path@version.
With no arguments, download applies to all dependencies of the main module
(equivalent to 'go mod download all').

The go command will automatically download modules as needed during ordinary
execution. The "go mod download" command is useful mainly for pre-filling
the local cache or to compute the answers for a Go module proxy.

By default, download writes nothing to standard output. It may print progress
messages and errors to standard error.

The -json flag causes download to print a sequence of JSON objects
to standard output, describing each downloaded module (or failure),
corresponding to this Go struct:

    type Module struct {
        Path     string // 模块路径
        Version  string // 模块版本
        Error    string // 加载模块时出错
        Info     string // 缓存的.info文件的绝对路径
        GoMod    string // 缓存的.mod文件的绝对路径
        Zip      string // 缓存的.zip文件的绝对路径
        Dir      string // 缓存的源根目录的绝对路径
        Sum      string // 路径的校验和，版本（如go.sum）
        GoModSum string // go.mod的校验和（如go.sum）
    }

The -x flag causes download to print the commands download executes.

See https:// golang.org/ref/mod#go mod download了解更多关于go mod download的信息。

See https:// org/ref/mod#version querys了解有关版本查询的更多信息。
	`,
}

var downloadJSON = cmdDownload.Flag.Bool("json", false, "")

func init() {
	cmdDownload.Run = runDownload // 中断初始循环

	// TODO（jayconrod）：https:
	cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
	base.AddModCommonFlags(&cmdDownload.Flag)
}

type moduleJSON struct {
	Path     string `json:",omitempty"`
	Version  string `json:",omitempty"`
	Error    string `json:",omitempty"`
	Info     string `json:",omitempty"`
	GoMod    string `json:",omitempty"`
	Zip      string `json:",omitempty"`
	Dir      string `json:",omitempty"`
	Sum      string `json:",omitempty"`
	GoModSum string `json:",omitempty"`
}

func runDownload(ctx context.Context, cmd *base.Command, args []string) {
	// 检查模块是否已启用，以及我们是否在模块中。
	modload.ForceUseModules = true
	if !modload.HasModRoot() && len(args) == 0 {
		base.Fatalf("go mod download: no modules specified (see 'go help mod download')")
	}
	haveExplicitArgs := len(args) > 0
	if !haveExplicitArgs {
		args = []string{"all"}
	}
	if modload.HasModRoot() {
		modload.LoadModFile(ctx) // 完成目标
		targetAtUpgrade := modload.Target.Path + "@upgrade"
		targetAtPatch := modload.Target.Path + "@patch"
		for _, arg := range args {
			switch arg {
			case modload.Target.Path, targetAtUpgrade, targetAtPatch:
				os.Stderr.WriteString("go mod download: skipping argument " + arg + " that resolves to the main module\n")
			}
		}
	}

	downloadModule := func(m *moduleJSON) {
		var err error
		m.Info, err = modfetch.InfoFile(m.Path, m.Version)
		if err != nil {
			m.Error = err.Error()
			return
		}
		m.GoMod, err = modfetch.GoModFile(m.Path, m.Version)
		if err != nil {
			m.Error = err.Error()
			return
		}
		m.GoModSum, err = modfetch.GoModSum(m.Path, m.Version)
		if err != nil {
			m.Error = err.Error()
			return
		}
		mod := module.Version{Path: m.Path, Version: m.Version}
		m.Zip, err = modfetch.DownloadZip(ctx, mod)
		if err != nil {
			m.Error = err.Error()
			return
		}
		m.Sum = modfetch.Sum(mod)
		m.Dir, err = modfetch.Download(ctx, mod)
		if err != nil {
			m.Error = err.Error()
			return
		}
	}

	var mods []*moduleJSON
	type token struct{}
	sem := make(chan token, runtime.GOMAXPROCS(0))
	infos, infosErr := modload.ListModules(ctx, args, 0)
	if !haveExplicitArgs {
		// “go mod download”有时在没有参数的情况下运行，以预填充
		// 模块缓存。它可能获取构建包不需要的模块
		// 在主菜单中。这通常不是有意的，所以不要为你的计划节省资金
		// 下载模块（golang.org/issue/45332）。
		// TODO（golang.org/issue/45551）：目前，在ListModules中，保存所需的金额
		// 加载生成列表（与1.15行为相同）。将来，请报告
		// 如果加载生成后需要更新go.mod或go.sum，则出错
		// 列表
		modload.DisallowWriteGoMod()
	}

	for _, info := range infos {
		if info.Replace != nil {
			info = info.Replace
		}
		if info.Version == "" && info.Error == nil {
			// 主模块或用文件路径替换的模块。
			// 无需下载。
			continue
		}
		m := &moduleJSON{
			Path:    info.Path,
			Version: info.Version,
		}
		mods = append(mods, m)
		if info.Error != nil {
			m.Error = info.Error.Err
			continue
		}
		sem <- token{}
		go func() {
			downloadModule(m)
			<-sem
		}()
	}

	// 填充信号通道以等待goroutines完成。
	for n := cap(sem); n > 0; n-- {
		sem <- token{}
	}

	if *downloadJSON {
		for _, m := range mods {
			b, err := json.MarshalIndent(m, "", "\t")
			if err != nil {
				base.Fatalf("go mod download: %v", err)
			}
			os.Stdout.Write(append(b, '\n'))
			if m.Error != "" {
				base.SetExitStatus(1)
			}
		}
	} else {
		for _, m := range mods {
			if m.Error != "" {
				base.Errorf("go mod download: %v", m.Error)
			}
		}
		base.ExitIfErrors()
	}

	// 如果有显式参数，请更新go.mod，尤其是go.sum。
	// “去下载mod@version'是一种在不使用
	// “去吧mod@version，可能还有其他副作用。我们把这个打印出来
	// 一些错误消息提示。
	// None
	// 不要为没有参数的“go mod download”保存总数；见上面的评论。
	if haveExplicitArgs {
		modload.WriteGoMod(ctx)
	}

	// 如果匹配某些请求的包时出现错误，请立即发出该错误
	// （在我们为下载的模块编写校验和之后
	// 成功地）。
	if infosErr != nil {
		base.Errorf("go mod download: %v", infosErr)
	}
}
